package cn.acmenms.controller;

import cn.acmenms.annotation.IgnoreSecurity;
import cn.acmenms.exception.BusinessException;
import cn.acmenms.manager.authorization.JwtTokenManager;
import cn.acmenms.manager.authorization.TokenManager;
import cn.acmenms.manager.redis.RedisManager;
import cn.acmenms.po.enums.ResponseCode;
import cn.acmenms.po.model.User;
import cn.acmenms.po.vo.AjaxResponse;
import cn.acmenms.po.vo.TokenUser;
import cn.acmenms.service.UserService;
import cn.acmenms.utils.Constants;
import cn.acmenms.utils.LocalDateTimeUtils;
import cn.acmenms.utils.StringTools;
import org.patchca.color.SingleColorFactory;
import org.patchca.filter.predefined.CurvesRippleFilterFactory;
import org.patchca.service.ConfigurableCaptchaService;
import org.patchca.utils.encoder.EncoderHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName: UserController
 * @Description: 用户Controller
 * @author: 神奇的鸭鸭
 * @Date: 2017/12/28 13:48
 */
@RestController
public class UserController extends BaseController {

    private Logger logger = LoggerFactory.getLogger(UserController.class);

    @Resource
    private UserService userService;

    @Resource
    private RedisManager redisManager;

    /**
     * 注册
     *
     * @param request   request
     * @param user      user
     * @param checkCode checkCode
     * @return result
     */
    @IgnoreSecurity
    @RequestMapping(value = "/user/register", method = RequestMethod.POST)
    public AjaxResponse<Object> register(HttpServletRequest request, @RequestBody User user, String checkCode, Boolean remember, String codeId) {
        // 是否记住密码
        Boolean REMEMBER = true;
        String token;
        AjaxResponse<Object> result = new AjaxResponse<>();
        TokenManager tokenManager = new JwtTokenManager();
        result.setResponseCode(ResponseCode.SUCCESS);
        try {
            // 需要验证码验证
            if (codeId != null) {
                String CurCheckCode = redisManager.get(codeId);
                if (StringTools.isNotEmpty(CurCheckCode) && !checkCode.equalsIgnoreCase(CurCheckCode)) {
                    result.setErrorMsg("验证码错误");
                    result.setResponseCode(ResponseCode.CODEERROR);
                    return result;
                }
            }
            String lastIp = this.getIpAddr(request);
            user.setLastLoginIp(lastIp);
            user.setIpAddress(lastIp);
            // 调用register业务
            userService.register(user);
            // 自动登录
            TokenUser tokenUser = new TokenUser();
            tokenUser.setUserId(user.getUserId());
            tokenUser.setUserName(user.getUserName());
            tokenUser.setUserIcon(user.getUserIcon());
            if (REMEMBER.equals(remember)) {
                // 有效时间7天
                LocalDateTime aWeek = LocalDateTimeUtils.plus(LocalDateTime.now(), 7, ChronoUnit.DAYS);
                token = tokenManager.createToken(tokenUser, LocalDateTimeUtils.getMilliByTime(aWeek));
                tokenUser.setToken(token);
            } else {
                // 一小时后过期
                LocalDateTime aWeek = LocalDateTimeUtils.plus(LocalDateTime.now(), 1, ChronoUnit.HOURS);
                token = tokenManager.createToken(tokenUser, LocalDateTimeUtils.getMilliByTime(aWeek));
                tokenUser.setToken(token);
            }
            result.setData(tokenUser);
        } catch (BusinessException e) {
            result.setErrorMsg(e.getMessage());
            result.setResponseCode(ResponseCode.BUSINESSERROR);
            logger.error("注册用户失败,用户名{},邮箱：{}", user.getUserName(), user.getEmail());
        } catch (Exception e) {
            result.setErrorMsg(ResponseCode.SERVERERROR.getDesc());
            result.setResponseCode(ResponseCode.SERVERERROR);
            logger.error("注册用户失败,用户名{},邮箱：{}", user.getUserName(), user.getEmail());
        }
        return result;
    }

    /**
     * 登录接口
     *
     * @param request     请求
     * @param nameOrEmail 用户名/邮箱
     * @param password    密码
     * @param remember    是否记住密码
     * @param checkCode   验证码
     * @return token和用户信息
     */
    @IgnoreSecurity
    @RequestMapping(value = "/user/login", method = RequestMethod.GET)
    public AjaxResponse<TokenUser> login(HttpServletRequest request, String nameOrEmail, String password, Boolean remember, String checkCode, String codeId) {
        // 是否记住密码
        Boolean REMEMBER = true;
        User user;
        TokenManager tokenManager = new JwtTokenManager();
        AjaxResponse<TokenUser> result = new AjaxResponse<>();
        result.setResponseCode(ResponseCode.SUCCESS);

        // 校验当前账号登录是否频繁
        String loginErrorCount = redisManager.get(nameOrEmail);
        try {
            // 登录频繁需要验证码拦截
            if (null != loginErrorCount && Long.valueOf(loginErrorCount) + 1 >= Constants.MAX_LOGIN_ERROR_COUNT) {
                // 没有收到验证码
                if (null == checkCode) {
                    result.setErrorMsg("账号异常，请输入验证码！");
                    result.setResponseCode(ResponseCode.MOREMAXLOGINCOUNT);
                    return result;
                }
                // 有验证码 匹配验证码 验证码方法：sendCheckCode
                String cacheCheckCode = redisManager.get(codeId);
                if (StringTools.isNotEmpty(cacheCheckCode) && !cacheCheckCode.equalsIgnoreCase(checkCode)) {
                    result.setErrorMsg("验证码错误");
                    result.setResponseCode(ResponseCode.CODEERROR);
                    return result;
                }
                // 通过验证码拦截考验，开始调用业务login，校验失败直接抛出异常
                user = userService.login(nameOrEmail, password, Constants.ENCODE_PWD, this.getIpAddr(request));
            } else {
                // 无需验证码验证，开始调用业务login，校验失败直接抛出异常
                user = userService.login(nameOrEmail, password, Constants.ENCODE_PWD, this.getIpAddr(request));
            }
            // 生成Token
            TokenUser tokenUser = new TokenUser();
            tokenUser.setUserId(user.getUserId());
            tokenUser.setUserName(user.getUserName());
            tokenUser.setUserIcon(user.getUserIcon());
            // 记住登陆状态
            if (REMEMBER.equals(remember)) {
                // 有效时间7天
                LocalDateTime aWeek = LocalDateTimeUtils.plus(LocalDateTime.now(), 7, ChronoUnit.DAYS);
                String token = tokenManager.createToken(tokenUser, LocalDateTimeUtils.getMilliByTime(aWeek));
                tokenUser.setToken(token);
            } else {
                // 一小时后过期
                LocalDateTime aWeek = LocalDateTimeUtils.plus(LocalDateTime.now(), 1, ChronoUnit.HOURS);
                String token = tokenManager.createToken(tokenUser, LocalDateTimeUtils.getMilliByTime(aWeek));
                tokenUser.setToken(token);
            }
            result.setData(tokenUser);
        } catch (BusinessException e) {
            // 账号存在 就记录错误次数
            if (!e.getMessage().equals("用户不存在")) {
                Long oldNumber = redisManager.increment(nameOrEmail, 1);
                redisManager.expire(nameOrEmail, 7, TimeUnit.DAYS);
                if (oldNumber > Constants.MAX_LOGIN_ERROR_COUNT) {
                    result.setResponseCode(ResponseCode.MOREMAXLOGINCOUNT);
                }
            }
            if (result.getResponseCode() == ResponseCode.SUCCESS) {
                result.setResponseCode(ResponseCode.BUSINESSERROR);
            }
            result.setErrorMsg(e.getMessage());
            logger.error("登录失败，用户名/邮箱：{}", nameOrEmail);
        } catch (Exception e) {
            result.setResponseCode(ResponseCode.SERVERERROR);
            result.setErrorMsg(ResponseCode.SERVERERROR.getDesc());
            logger.error("登录失败，用户名/邮箱：{}", nameOrEmail);
        }
        // 登录完毕，释放异常记录
        if (result.getResponseCode() == ResponseCode.SUCCESS) {
            redisManager.delete(nameOrEmail);
        }
        return result;
    }

    /**
     * 生成验证码图片
     *
     * @param response 响应头
     */
    @IgnoreSecurity
    @RequestMapping(value = "/user/checkCode", method = RequestMethod.GET)
    public void checkCode(HttpServletResponse response, String codeId) throws IOException {
        if (codeId == null) {
            return;
        }
        ConfigurableCaptchaService cs = new ConfigurableCaptchaService();
        cs.setColorFactory(new SingleColorFactory(new Color(16, 142, 233)));
        cs.setFilterFactory(new CurvesRippleFilterFactory(cs.getColorFactory()));
        // 设置宽度
        cs.setWidth(280);
        // 设置高度
        cs.setHeight(60);
        // 设置验证码
        cs.setWordFactory(UserController::getCode);
        // 设置响应头
        response.setHeader("Pragma", "no-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);
        response.setContentType("image/jpeg");
        String code = EncoderHelper.getChallangeAndWriteImage(cs, "png", response.getOutputStream());
        // Redis存储验证码字符串，过期时间为1分钟
        redisManager.setForTimeMIN(codeId, code, 1);
    }

    /**
     * 真正生成验证码的方法
     *
     * @return checkCode
     */
    private static String getCode() {
        char[] codeSequence = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R',
                'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '2', '3', '4', '5', '6', '7', '8', '9'};
        StringBuilder randomCode = new StringBuilder();
        // 随机产生codeCount数字的验证码。
        Random random = new Random();
        int codeCount = random.nextInt(4) + 3;
        int codeSequenceLength = codeSequence.length;
        for (int i = 0; i < codeCount; i++) {
            String strRand = String.valueOf(codeSequence[random.nextInt(codeSequenceLength)]);
            // 得到随机产生的验证码数字。
            randomCode.append(strRand);
        }
        return randomCode.toString();
    }
}